package indi.defoliation.unknownrpg.api;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;

import org.bukkit.event.Listener;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import indi.defoliation.unknownrpg.api.attribute.Attribute;
import indi.defoliation.unknownrpg.api.attribute.AttributeBody;
import indi.defoliation.unknownrpg.api.attribute.AttributeEventHandler;
import indi.defoliation.unknownrpg.api.attribute.AttributePriority;
import indi.defoliation.unknownrpg.api.rpgevent.Event;
import indi.defoliation.unknownrpg.api.rpgevent.entity.EntityEvent;

import static indi.defoliation.unknownrpg.core.UnknownRPG.sendMessage;
/*
 * 
 * 浜嬩欢杩涘叆
 * 
 * 鎶涚粰灞炴�э紝杩囩▼
 * 
 * 
 * 缁撶畻
 * 
 */

public class RPGItemManager {

	private static ArrayList<Attribute> attributeList = new ArrayList<>();

	private static Event EVENT = new Event();

	private static EntityEvent ENTITY_EVENT = new EntityEvent(null);

	private static EnumMap<AttributePriority, HashMap<String, ArrayList<MethodWrapper>>> priorityMap = new EnumMap<>(
			AttributePriority.class);
	
	/**
	 * 注册你的属性.
	 * <br>
	 * 当你注册属性时，属性内的事件也会一同注册
	 * 
	 * 
	 * @param attribute 属性
	 */
	public static void registerAttribute(Attribute attribute) {
		sendMessage("registe "+attribute.getDisplayName());
		registerListener(attribute);
		attributeList.add(attribute.clone());
	}
	
	/**
	 * 注册你的事件.
	 * <br>
	 * 只有被{@link indi.defoliation.unknownrpg.api.attribute.Attribute.EventHandler}注释
	 * <br>
	 * 且第一个参数参数为{@link indi.defoliation.unknownrpg.api.rpgevent.Event}的子类的方法才会被注册
	 * <br>
	 * 方法有两种形式
	 * <br>
	 * 第一种为一般的
	 * <br>
	 * public void methodName(Event e){}
	 * <br>
	 * 另一种为
	 * <br>
	 * public void methodName(Event e,Map<AttributeBody,Attribute> map){}
	 * <br>
	 * 这种写法下你必须监听的是{@link indi.defoliation.unknownrpg.api.rpgevent.entity.EntityEvent}的子类的事件才有效。
	 * <br>
	 * 第二个参数为你的属性对应Entity事件内的Entity身上位置的装备。
	 * <br>
	 * 如果你只需某个部位的属性请参见{@link indi.defoliation.unknownrpg.api.attribute.Attribute.AttributeEventHandler}
	 * 
	 * @param listener
	 */
	public static void registerListener(Listener listener) {
		for (Method method : listener.getClass().getMethods()) {
			boolean hasMapPriorityt = false;
			AttributeEventHandler hanlder = method.getAnnotation(AttributeEventHandler.class);
			if (hanlder == null)
				continue;

			Class<?>[] parametric = method.getParameterTypes();

			if (parametric.length > 2 && parametric.length == 0)
				continue;

			if (!EVENT.getClass().isAssignableFrom(parametric[0]))
				continue;

			if (parametric.length == 2) {
				if (!Map.class.getName().equals(parametric[1].getName()))
					continue;
				if (!checkMapGeneric(method))
					continue;

				if (!ENTITY_EVENT.getClass().isAssignableFrom(parametric[0]))
					continue;
				hasMapPriorityt = true;
			}

			ArrayList<MethodWrapper> attributeList = getAttributeWrapperListIfNullReturnNewAndPut(hanlder.priority(),
					parametric[0].getName());

			attributeList.add(new MethodWrapper(listener, method, hanlder.body(),
							hanlder.ignoreNull(), hasMapPriorityt));
		}
	}

	private static boolean checkMapGeneric(Method method) {
		String[] str = method.getGenericParameterTypes()[1].getTypeName().split("[<>]");
		if (str.length != 2)
			return false;
		str = str[1].split("[,]");
		if (str.length != 2)
			return false;
		if (!AttributeBody.class.getName().equals(str[0].trim()))
			return false;
		if (!Attribute.class.getName().equals(str[1].trim()))
			return false;
		return true;
	}

	private static ArrayList<MethodWrapper> getAttributeWrapperListIfNullReturnNewAndPut(AttributePriority priority,
			String eventName) {
		HashMap<String, ArrayList<MethodWrapper>> eventMap = getEventMapIfNullReturnNewAndPut(priority);
		ArrayList<MethodWrapper> list = eventMap.get(eventName);
		if (list == null) {
			list = Lists.newArrayList();
			eventMap.put(eventName, list);
		}
		return list;
	}

	private static HashMap<String, ArrayList<MethodWrapper>> getEventMapIfNullReturnNewAndPut(
			AttributePriority priority) {
		HashMap<String, ArrayList<MethodWrapper>> map = priorityMap.get(priority);
		if (map == null) {
			map = Maps.newHashMap();
			priorityMap.put(priority, map);
		}
		return map;
	}

	public static void callEvent(Event e) {

		for (AttributePriority priority : AttributePriority.values()) {

			for (MethodWrapper wrapper : getAttributeWrapperListIfNullReturnNewAndPut(priority,
					e.getClass().getName())) {

				if (wrapper.hasMapPriorityt) {
					if (!(e instanceof EntityEvent))
						continue;

					EntityEvent entityEvent = (EntityEvent) e;

					EnumMap<AttributeBody, Attribute> map = new EnumMap<>(AttributeBody.class);

					for (AttributeBody body : wrapper.body) {
						for (Attribute att : RPGItemHelper.getEntityRPGItemsOnBody(entityEvent.getEntity(), body)
								.getAttributes()) {
							if (wrapper.attribute.getClass().getName().equals(att.getClass().getName()))
								map.put(body, att);
						}
					}

					if (wrapper.ignore && map.isEmpty())
						continue;

					try {
						wrapper.method.invoke(wrapper.attribute, e, map);
					} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e1) {
						e1.printStackTrace();
					}

				} else
					try {
						wrapper.method.invoke(wrapper.attribute, e);
					} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e1) {
						e1.printStackTrace();
					}
			}
		}
	}

	public static Attribute[] getRegisteredAttributes() {
		return attributeList.toArray(new Attribute[0]);
	}

	private static class MethodWrapper {
		public final AttributeBody[] body;
		public final boolean ignore;
		public final Method method;
		public final Listener attribute;
		public final boolean hasMapPriorityt;

		public MethodWrapper(Listener attribute, Method method, AttributeBody[] body, boolean ignore,
				boolean hasMapPriorityt) {
			this.attribute = attribute;
			this.body = body;
			this.ignore = ignore;
			this.method = method;
			this.hasMapPriorityt = hasMapPriorityt;
		}

	}
}
